package com.ruoyi.web.service.impl;

import com.gitee.deeround.jdbchelper.DbAction;
import com.gitee.deeround.jdbchelper.resultSetExtractors.CustomApply;
import com.ruoyi.cache.util.CacheUtil;
import com.ruoyi.core.constant.Constant;
import com.ruoyi.core.entity.BaseMap;
import com.ruoyi.core.entity.KV;
import com.ruoyi.core.entity.TokenInfo;
import com.ruoyi.core.util.CaptchaUtil;
import com.ruoyi.core.util.IdUtil;
import com.ruoyi.core.util.RequestContextUtil;
import com.ruoyi.encrypt.util.SM3Util;
import com.ruoyi.web.context.RequestContextHolder;
import com.ruoyi.web.domain.SysLogLogin;
import com.ruoyi.web.exception.ApiException;
import com.ruoyi.web.mapper.SysLogLoginMapper;
import com.ruoyi.web.service.LoginService;
import com.ruoyi.web.service.SysConfigService;
import com.ruoyi.web.util.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
public class LoginServiceImpl implements LoginService {

    @Autowired
    private DbAction dbAction;

    @Autowired
    private SysLogLoginMapper sysLogLoginMapper;

    @Autowired
    private SysConfigService sysConfigService;

    @Override
    public KV getAuthCode() {
        String key = IdUtil.simpleUUID();
        String randomString = CaptchaUtil.getRandomString();
        String randomBase64 = CaptchaUtil.getRandomBase64(randomString);
        CacheUtil.putAuthCode(key, randomString);
        KV kv = new KV(key, randomBase64);
        return kv;
    }

    @Override
    public String login(String username, String password, String authKey, String authCode) {

        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
            throw new ApiException("用户名或密码不能为空");
        }
        if (StringUtils.isEmpty(authKey) || StringUtils.isEmpty(authCode)) {
            throw new ApiException("验证码不能为空");
        }

        //验证验证码
        if (!authCode.equalsIgnoreCase(CacheUtil.getAuthCode(authKey))) {
            CacheUtil.removeAuthCode(authKey);
            throw new ApiException("验证码不正确或已失效");
        }
        CacheUtil.removeAuthCode(authKey);

        try {
            //验证账号密码
            BaseMap user = dbAction.queryForObject("select * from sys_user where user_name=?", new CustomApply<BaseMap>() {
                @Override
                public BaseMap apply(Map map) {
                    return new BaseMap(map);
                }
            }, username);

            if (user == null) {
                throw new ApiException("用户名或密码不正确");
            }

            if (Constant.NO.equals(user.getString("status"))) {
                throw new ApiException("用户已停用");
            }

            if (Constant.YES.equals(user.getString("locked"))) {
                throw new ApiException("用户已锁定");
            }

            if (!SM3Util.encrypt(password).equals(user.getString("password"))) {
                dbAction.update("update sys_user set login_fail_num=login_fail_num+1 where user_name=?", new Object[]{username});

                Integer login_fail_max_num = sysConfigService.getIntegerValueByKey("LOGIN_FAIL_MAX_NUM", 0);
                if (login_fail_max_num > 0 && user.getInteger("login_fail_num") + 1 > login_fail_max_num) {
                    dbAction.update("update sys_user set locked='1' where user_name=?", new Object[]{username});
                }

                throw new ApiException("用户名或密码不正确");
            }

            //生成token
            TokenInfo tokenInfo = new TokenInfo();
            tokenInfo.setUserId(user.getString("user_id"));
            tokenInfo.setUserName(user.getString("user_name"));
            tokenInfo.setUserType(user.getString("user_type"));
            tokenInfo.setNickName(user.getString("nick_name"));

            tokenInfo.setDeptId(user.getString("dept_id"));
            tokenInfo.setDeptName(user.getString("dept_name"));

            List<BaseMap> list = dbAction.queryForListObject("select a.* from sys_role a,sys_user_role b where a.role_id=b.role_id and b.user_id=?", new CustomApply<BaseMap>() {
                @Override
                public BaseMap apply(Map<String, Object> map) {
                    return new BaseMap(map);
                }
            }, user.getString("user_id"));
            List<TokenInfo.RoleInfo> roles = new ArrayList<>();
            for (BaseMap role : list) {
                roles.add(new TokenInfo.RoleInfo(role.getString("role_id"), role.getString("role_name")));
            }
            tokenInfo.setRoles(roles);


            String token = JwtUtil.createToken(tokenInfo);

            //存入缓存
            CacheUtil.putAuthToken(token, "1");

            handleLog(username, null);

            if (user.getInteger("login_fail_num") > 0) {
                dbAction.update("update sys_user set login_fail_num=0 where user_name=?", new Object[]{username});
            }
            HttpServletRequest request = RequestContextUtil.getRequest();
            dbAction.update("update sys_user set login_ip=?,login_date=? where user_name=?", new Object[]{RequestContextUtil.getRequestIp(request), new Date(), username});

            return token;
        } catch (Exception e) {
            handleLog(username, e);
            throw e;
        }
    }

    @Override
    public void logout() {
        //移除缓存
        CacheUtil.removeAuthToken(RequestContextHolder.currentToken());
    }

    private void handleLog(String userName, final Exception e) {
        try {
            HttpServletRequest request = RequestContextUtil.getRequest();
            SysLogLogin operLog = new SysLogLogin();
            operLog.setStatus(Constant.YES);
            operLog.setOperTime(new Date());
            operLog.setIpaddr(RequestContextUtil.getRequestIp(request));
            operLog.setBrowser(RequestContextUtil.getRequestBrowser(request));
            operLog.setUserName(userName);
            if (e != null) {
                operLog.setStatus(Constant.NO);
                operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
            }
            sysLogLoginMapper.insert(operLog);
        } catch (Exception exp) {
            // 记录本地异常日志
            log.error("记录登录日志异常:{}", exp.getMessage());
            exp.printStackTrace();
        }
    }
}
